import visitor.*;
import syntaxtree.*;
import symboltable;

public class TypeCheckVisitor implements TypeVisitor {

  private static Class currClass;
  private static Method currMethod;

  private static SymbolTable symbolTable;

  public TypeCheckVisitor(SymbolTable st) {
    symbolTable = st;
  }


  // MainClass m;
  // ClassDeclList cl;
  public Type visit(Program n) {
    n.m.accept(this);
    for ( int i = 0; i < n.cl.size(); i++ ) {
        n.cl.elementAt(i).accept(this);
    }
    return null;
  }

  
  // Identifier i1,i2;
  // Statement s;
  public Type visit(MainClass n) {
    String i1 = n.i1.toString();
    currClass = symbolTable.getClass(i1);
    n.i2.accept(this);
    n.s.accept(this);
    return null;
  }
  


  // Identifier i;
  // VarDeclList vl;
  // MethodDeclList ml;
  public Type visit(ClassDeclSimple n) {
    n.i.accept(this);
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.ml.size(); i++ ) {
        n.ml.elementAt(i).accept(this);
    }
    return null;
  }


  // Identifier i;
  // Identifier i;
  // VarDeclList vl;
  // MethodDeclList ml;
  public Type visit(ClassDeclExtends n) {
    n.i.accept(this);
    n.j.accept(this);
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.ml.size(); i++ ) {
        n.ml.elementAt(i).accept(this);
    }
    return null;
  }



  // Type t;
  // Identifier i;
  public Type visit(VarDecl n) {
    n.t.accept(this);
    n.i.accept(this);
    return null;
  }



  // Type t;
  // Identifier i;
  // FormalList fl;
  // VarDeclList vl;
  // StatementList sl;
  // Exp e;
  public Type visit(MethodDecl n) {
    n.t.accept(this);
    String id = n.i.toString();
    currMethod = currClass.getMethod(id);
    Type retorno = currMethod.type();
    for ( int i = 0; i < n.fl.size(); i++ ) {
        n.fl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.sl.size(); i++ ) {
        n.sl.elementAt(i).accept(this);
    }
     if (symbolTable.compareTypes(retorno, n.e.accept(this))==false){
        System.out.println("Erro no tipo de retorno para o metodo "+ id);
        System.exit(0);
    }
    return null;
  }



  // Type t;
  // Identifier i;
  public Type visit(Formal n) {
    n.t.accept(this);
    n.i.accept(this);
    return null;
  }

  public Type visit(IntArrayType n) {
    return null;
  }

  public Type visit(BooleanType n) {
    return null;
  }

  public Type visit(IntegerType n) {
    return null;
  }

  // String s;
  public Type visit(IdentifierType n) {
    return null;
  }

  // StatementList sl;
  public Type visit(Block n) {
    for ( int i = 0; i < n.sl.size(); i++ ) {
        n.sl.elementAt(i).accept(this);
    }
    return null;
  }



  // Exp e;
  // Statement s1,s2;
  public Type visit(If n) {
    if (! (n.e.accept(this) instanceof BooleanType) ) {
       System.out.println("A condicao de um IF deve ser do tipo boolean");
       System.exit(-1);
    }
    n.s1.accept(this);
    n.s2.accept(this);
    return null;
  }


  // Exp e;
  // Statement s;
  public Type visit(While n) {
    if (! (n.e.accept(this) instanceof BooleanType) ) {
       System.out.println("A condicao de um WHILE deve ser do tipo boolean");
       System.exit(-1);
    }
    n.s.accept(this);
    return null;
  }

  // Exp e;
  public Type visit(Print n) {
    if (! (n.e.accept(this) instanceof IntegerType) ) {
       System.out.println("O argumento para System.out.println deve ser do tipo inteiro");
       System.exit(-1);
    }
    return null;
  }
  

  // Identifier i;
  // Exp e;
  public Type visit(Assign n) {
    Type t1 = symbolTable.getVarType(currMethod,currClass,n.i.toString());
    Type t2 = n.e.accept(this);
    if (symbolTable.compareTypes(t1,t2)==false){
        System.out.println("Erro de tipo na atribuicao para "+n.i.toString());       
        System.exit(0);
    }
    return null;
  }


  // Identifier i;
  // Exp e1,e2;
  public Type visit(ArrayAssign n) {
    n.i.accept(this);
    n.e1.accept(this);
    n.e2.accept(this);
    return null;
  }

  // Exp e1,e2;
  public Type visit(And n) {
    if (! (n.e1.accept(this) instanceof BooleanType) ) {
       System.out.println("Lado esquerdo do AND deve ser do tipo booleano");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof BooleanType) ) {
       System.out.println("Lado direito do AND deve ser do tipo booleano");
       System.exit(-1);
    }
    return new BooleanType();
  }

  // Exp e1,e2;
  public Type visit(LessThan n) {
    if (! (n.e1.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado esquerdo do LessThan deve ser do tipo inteiro");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado direito do LessThan deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new BooleanType();
  }

  // Exp e1,e2;
  public Type visit(Plus n) {
    if (! (n.e1.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado esquerdo do Plus deve ser do tipo inteiro");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado direito do Plus deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(Minus n) {
    if (! (n.e1.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado esquerdo do Minus deve ser do tipo inteiro");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado direito do Minus deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(Times n) {
    if (! (n.e1.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado esquerdo do Times deve ser do tipo inteiro");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof IntegerType) ) {
       System.out.println("Lado direito do Times deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(ArrayLookup n) {
    if (! (n.e1.accept(this) instanceof IntArrayType) ) {
       System.out.println("alvo de um ArrayLookup deve ser do tipo IntArray");
       System.exit(-1);
    }
    if (! (n.e2.accept(this) instanceof IntegerType) ) {
       System.out.println("indice do ArrayLookup deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new IntegerType();
  }

  // Exp e;
  public Type visit(ArrayLength n) {
    if (! (n.e.accept(this) instanceof IntArrayType) ) {
       System.out.println("alvo do ArrayLength deve ser do tipo Array");
       System.exit(-1);
    }
    return new IntegerType();
  }


  // Exp e;
  // Identifier i;
  // ExpList el;
  public Type visit(Call n) {

    if (! (n.e.accept(this) instanceof IdentifierType)){
        System.out.println("Metodo "+ n.i.toString() + "chamado fora de classe ou Objeto.");
        System.exit(-1);
    } 

    String nomemetodo = n.i.toString();    
    String nomeclasse = ((IdentifierType) n.e.accept(this)).s;

    Method metodochamado = TypeCheckVisitor.symbolTable.getMethod(nomemetodo,nomeclasse);
    
    for ( int i = 0; i < n.el.size(); i++ ) {           
        Type t1 =null;  
        Type t2 =null;  

        if (metodochamado.getParamAt(i)!=null)
            t1 = metodochamado.getParamAt(i).type();
        t2 = n.el.elementAt(i).accept(this);
        if (!TypeCheckVisitor.symbolTable.compareTypes(t1,t2)){
            System.out.println("Erro de tipo no argumento passado para " + nomeclasse +"." + nomemetodo);
            System.exit(-1);  
        }           
    }

    return TypeCheckVisitor.symbolTable.getMethodType(nomemetodo,nomeclasse);
  }



  // int i;
  public Type visit(IntegerLiteral n) {
    return new IntegerType();
  }

  public Type visit(True n) {
    return new BooleanType();
  }

  public Type visit(False n) {
    return new BooleanType();
  }

  // String s;
  public Type visit(IdentifierExp n) {      
    return TypeCheckVisitor.symbolTable.getVarType(TypeCheckVisitor.currMethod,TypeCheckVisitor.currClass,n.s);
  }

  public Type visit(This n) {
      return TypeCheckVisitor.currClass.type();
  }

  // Exp e;
  public Type visit(NewArray n) {
    
    if (! (n.e.accept(this) instanceof IntegerType) ) {
       System.out.println("Expressao Array em NewArray deve ser do tipo inteiro");
       System.exit(-1);
    }
    return new IntArrayType();
  }

  // Identifier i;
  public Type visit(NewObject n) {
    return new IdentifierType(n.i.s);
  }

  // Exp e;
  public Type visit(Not n) {
    if (! (n.e.accept(this) instanceof BooleanType) ) {
       System.out.println("Expressao NOT deve ser do tipo boolean");
       System.exit(-1);
    }
    return new BooleanType();
  }

  // String s;
  public Type visit(Identifier n) {
    n.s.accept(this);
    return null;
  }
}










